﻿#pragma once

#include <Geom2d_BSplineCurve.hxx>
#include <Geom_BSplineCurve.hxx>
#include <Geom_BSplineSurface.hxx>
#include <TColStd_Array1OfInteger.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TColgp_Array1OfPnt.hxx>
#include <TColgp_Array1OfPnt2d.hxx>
#include <TColgp_Array2OfPnt.hxx>

#include <learn/geom/geom_bspline_curve.h>
#include <learn/geom/geom_bspline_surface.h>
#include <learn/geom/vec.h>

#include <tuple>
#include <variant>
#include <vector>

namespace xi
{

namespace utils
{

template <std::size_t N> struct Adaptor_OCCVec
{
    Adaptor_OCCVec(Adaptor_OCCVec &&) = delete;

    const double *data;
    const bool flag{false};

    Adaptor_OCCVec(const gp_Pnt &p) : data(p.XYZ().GetData())
    {
    }

    Adaptor_OCCVec(const gp_Pnt2d &p) : data(new double[2]{p.X(), p.Y()}), flag(true)
    {
    }

    Adaptor_OCCVec(const gp_Vec &p) : data(p.XYZ().GetData())
    {
    }

    Adaptor_OCCVec(const gp_Vec2d &p) : data(new double[2]{p.X(), p.Y()}), flag(true)
    {
    }

    Adaptor_OCCVec(const geom::VecT<N, double> &v) : data(v.data)
    {
    }

    ~Adaptor_OCCVec()
    {
        if (flag)
            delete[] data;
    }

    double operator[](std::size_t i) const
    {
        return data[i];
    }

    const double &operator[](std::size_t i)
    {
        return data[i];
    }

    operator gp_Pnt() const
    {
        static_assert(N == 3, "N must be 3");
        return gp_Pnt(data[0], data[1], data[2]);
    }

    operator gp_Pnt2d() const
    {
        static_assert(N == 2, "N must be 2");
        return gp_Pnt2d(data[0], data[1]);
    }

    operator gp_Vec() const
    {
        static_assert(N == 3, "N must be 3");
        return gp_Vec(data[0], data[1], data[2]);
    }

    operator gp_Vec2d() const
    {
        static_assert(N == 2, "N must be 2");
        return gp_Vec2d(data[0], data[1]);
    }

    operator geom::VecT<N, double>() const
    {
        geom::VecT<N, double> v;
        std::copy(data, data + N, v.data);
        return v;
    }
};

template <typename T> struct Adaptor_OCC1
{
    Adaptor_OCC1(Adaptor_OCC1 &&) = delete;

    union {
        const T *cdata;
        T *data;
    };

    const int size;
    const bool flag{false};

    explicit Adaptor_OCC1(std::nullptr_t) : data(nullptr), size(0)
    {
    }

    Adaptor_OCC1(const std::vector<T> &c) : cdata(c.data()), size(c.size())
    {
    }

    template <typename U>
    Adaptor_OCC1(const U *c)
        : data(c == nullptr ? nullptr : new T[c->Size()]), size(c == nullptr ? 0 : c->Size()), flag(c != nullptr)
    {
    }

    template <typename U> Adaptor_OCC1(const U &c) : data(new T[c.Size()]), size(c.Size()), flag(true)
    {
        if constexpr (std::is_same<T, gp_Pnt>::value || std::is_same<T, geom::Vec3>::value)
            for (int i = 0; i < size; ++i)
                data[i] = Adaptor_OCCVec<3>(c.Value(i + 1));
        else if constexpr (std::is_same<T, gp_Pnt2d>::value || std::is_same<T, geom::Vec2>::value)
            for (int i = 0; i < size; ++i)
                data[i] = Adaptor_OCCVec<2>(c.Value(i + 1));
        else
            for (int i = 0; i < size; ++i)
                data[i] = c.Value(i + 1);
    }

    ~Adaptor_OCC1()
    {
        if (flag)
            delete[] data;
    }

    T operator[](std::size_t i) const
    {
        return flag ? data[i] : cdata[i];
    }

    const T &operator[](std::size_t i)
    {
        return flag ? data[i] : cdata[i];
    }

    operator std::vector<T>() const
    {
        std::vector<T> v(size);
        auto data = flag ? this->data : this->cdata;
        std::copy(data, data + size, v.data());
        return v;
    }

    operator TColStd_Array1OfInteger() const
    {
        static_assert(std::is_same<T, int>::value, "T must be int");
        auto data = flag ? this->data : this->cdata;
        TColStd_Array1OfInteger a(1, size);
        for (int i = 0; i < size; ++i)
            a.SetValue(i + 1, data[i]);
        return a;
    }

    operator TColStd_Array1OfReal() const
    {
        static_assert(std::is_same<T, double>::value, "T must be double");
        auto data = flag ? this->data : this->cdata;
        TColStd_Array1OfReal a(1, size);
        for (int i = 0; i < size; ++i)
            a.SetValue(i + 1, data[i]);
        return a;
    }

    operator TColgp_Array1OfPnt() const
    {
        static_assert(std::is_same<T, gp_Pnt>::value || std::is_same<T, geom::Vec3>::value, "T must be gp_Pnt");
        auto data = flag ? this->data : this->cdata;
        TColgp_Array1OfPnt a(1, size);
        for (int i = 0; i < size; ++i)
            a.SetValue(i + 1, Adaptor_OCCVec<3>(data[i]));
        return a;
    }

    operator TColgp_Array1OfPnt2d() const
    {
        static_assert(std::is_same<T, gp_Pnt2d>::value || std::is_same<T, geom::Vec2>::value, "T must be gp_Pnt2d");
        auto data = flag ? this->data : this->cdata;
        TColgp_Array1OfPnt2d a(1, size);
        for (int i = 0; i < size; ++i)
            a.SetValue(i + 1, Adaptor_OCCVec<2>(data[i]));
        return a;
    }
};

struct Adaptor_OCCKnot
{
    Adaptor_OCCKnot(Adaptor_OCCKnot &&) = delete;

    std::variant<std::vector<double>, std::tuple<int, const double *>> data;

    Adaptor_OCCKnot(const std::vector<double> &knots) : data(std::make_tuple(knots.size(), knots.data()))
    {
    }

    Adaptor_OCCKnot(const TColStd_Array1OfReal &Knots, const TColStd_Array1OfInteger &Multiplier)
    {
        auto &data = std::get<0>(this->data);
        for (int i = 1; i <= Knots.Size(); i++)
            for (int j = 1; j <= Multiplier.Value(i); j++)
                data.push_back(Knots.Value(i));
    }

    operator std::vector<double>() const
    {
        if (std::get_if<0>(&data))
            return std::get<0>(data);
        else
        {
            auto [size, data] = std::get<1>(this->data);
            std::vector<double> knots(size);
            std::copy(data, data + size, knots.data());
            return knots;
        }
    }

    std::tuple<TColStd_Array1OfReal, TColStd_Array1OfInteger> occ() const
    {
        TColStd_Array1OfReal Knots;
        TColStd_Array1OfInteger Multiplier;

        int size = 0;
        if (std::get_if<0>(&data))
        {
            auto &data = std::get<0>(this->data);
            size = data.size();
            Knots.Resize(1, size, false);
            Multiplier.Resize(1, size, false);
        }
        else
        {
            auto [s, data] = std::get<1>(this->data);
            size = s;
            Knots.Resize(1, size, false);
            Multiplier.Resize(1, size, false);
        }

        const double *data =
            std::get_if<0>(&this->data) ? std::get<0>(this->data).data() : std::get<1>(std::get<1>(this->data));

        Multiplier.Init(0);
        Knots.SetValue(1, data[0]);
        Multiplier.SetValue(1, 1);

        int j = 1;
        for (int i = 1; i < size; i++)
        {
            if (std::abs(data[i] - Knots.Value(j)) < math::Numeric::half)
                Multiplier.SetValue(j, Multiplier.Value(j) + 1);
            else
            {
                j++;
                Knots.SetValue(j, data[i]);
                Multiplier.SetValue(j, 1);
            }
        }

        Knots.Resize(1, j, true);
        Multiplier.Resize(1, j, true);

        return std::make_tuple(Knots, Multiplier);
    }
};

template <std::size_t N, bool Rat = false> struct Adaptor_OCCBSplineCurve
{
    Adaptor_OCCBSplineCurve(Adaptor_OCCBSplineCurve &&) = delete;

    using Vec = geom::VecT<N, double>;
    using VecR = geom::VecT<N + Rat, double>;

    const bool flag{false};
    const int degree;
    Adaptor_OCCKnot uKnots;
    Adaptor_OCC1<VecR> cpts;
    Adaptor_OCC1<Vec> occCpts;
    Adaptor_OCC1<double> occWeights;

    // 不使用智能指针作为参数，因为可能由于智能指针自动析构导致内存被提前释放
    Adaptor_OCCBSplineCurve(geom::Geom_BSplineCurve<N, Rat> *curve)
        : flag(true), degree(curve->degree()), uKnots(curve->knots()), cpts(curve->poles()), occCpts(nullptr),
          occWeights(nullptr)
    {
    }

    Adaptor_OCCBSplineCurve(Handle(Geom_BSplineCurve) curve)
        : degree(curve->Degree()), uKnots(curve->Knots(), curve->Multiplicities()), cpts(nullptr),
          occCpts(curve->Poles()), occWeights(curve->Weights())
    {
    }

    Adaptor_OCCBSplineCurve(Handle(Geom2d_BSplineCurve) curve)
        : degree(curve->Degree()), uKnots(curve->Knots(), curve->Multiplicities()), cpts(nullptr),
          occCpts(curve->Poles()), occWeights(curve->Weights())
    {
    }

    operator std::shared_ptr<geom::Geom_BSplineCurve<N, Rat>>() const
    {
        std::vector<double> knots = uKnots;
        if (flag)
        {
            std::vector<VecR> poles = cpts;
            return std::make_shared<geom::Geom_BSplineCurve<N, Rat>>(std::move(poles), std::move(knots), degree);
        }
        else
        {
            auto &cpts = occCpts;
            auto &weights = occWeights;
            std::vector<VecR> poles(cpts.size);
            for (int i = 0; i < cpts.size; ++i)
            {
                for (int j = 0; j < N; ++j)
                    poles[i][j] = cpts[i][j];

                if constexpr (Rat)
                    poles[i][N] = weights.size > 0 ? weights[i] : 1.0;
            }
            return std::make_shared<geom::Geom_BSplineCurve<N, Rat>>(std::move(poles), std::move(knots), degree);
        }
    }

    operator Handle(Geom_BSplineCurve)() const
    {
        auto [Knots, Mult] = uKnots.occ();
        if (flag)
        {
            auto &cpts = this->cpts;
            if constexpr (Rat)
            {
                TColgp_Array1OfPnt vCpts(1, cpts.size);
                TColStd_Array1OfReal vWeights(1, cpts.size);
                for (int i = 0; i < cpts.size; ++i)
                {
                    Vec p;
                    for (int j = 0; j < N; ++j)
                        p[j] = cpts[i][j];

                    vCpts.SetValue(i + 1, Adaptor_OCCVec(p));
                    vWeights.SetValue(i + 1, cpts[i][N]);
                }
                return new Geom_BSplineCurve(vCpts, vWeights, Knots, Mult, degree);
            }
            else
            {
                TColgp_Array1OfPnt vCpts = cpts;
                return new Geom_BSplineCurve(vCpts, Knots, Mult, degree);
            }
        }
        else
        {
            if constexpr (Rat)
                return new Geom_BSplineCurve(occCpts, occWeights, Knots, Mult, degree);
            else
                return new Geom_BSplineCurve(occCpts, Knots, Mult, degree);
        }
    }

    operator Handle(Geom2d_BSplineCurve)() const
    {
        auto [Knots, Mult] = uKnots.occ();
        if (flag)
        {
            auto &cpts = this->cpts;
            if constexpr (Rat)
            {
                TColgp_Array1OfPnt2d vCpts(1, cpts.size);
                TColStd_Array1OfReal vWeights(1, cpts.size);
                for (int i = 0; i < cpts.size; ++i)
                {
                    Vec p;
                    for (int j = 0; j < N; ++j)
                        p[j] = cpts[i][j];

                    vCpts.SetValue(i + 1, Adaptor_OCCVec(p));
                    vWeights.SetValue(i + 1, cpts[i][N]);
                }
                return new Geom2d_BSplineCurve(vCpts, vWeights, Knots, Mult, degree);
            }
            else
            {
                TColgp_Array1OfPnt2d vCpts = cpts;
                return new Geom2d_BSplineCurve(vCpts, Knots, Mult, degree);
            }
        }
        else
        {
            if constexpr (Rat)
                return new Geom2d_BSplineCurve(occCpts, occWeights, Knots, Mult, degree);
            else
                return new Geom2d_BSplineCurve(occCpts, Knots, Mult, degree);
        }
    }
};

template <std::size_t N, bool Rat = false> struct Adaptor_OCCBSplineSurface
{
    Adaptor_OCCBSplineSurface(Adaptor_OCCBSplineSurface &&) = delete;

    using Vec = geom::VecT<N, double>;
    using VecR = geom::VecT<N + Rat, double>;

    const int uDegree, vDegree;
    const int uSize, vSize;
    Adaptor_OCCKnot uKnots, vKnots;
    Adaptor_OCC1<VecR> cpts;
    const TColgp_Array2OfPnt *occCpts{nullptr};
    const TColStd_Array2OfReal *occWeights{nullptr};

    // 不使用智能指针作为参数，因为可能由于智能指针自动析构导致内存被提前释放
    Adaptor_OCCBSplineSurface(geom::Geom_BSplineSurface<N, Rat> *surface)
        : uDegree(surface->u_degree()), vDegree(surface->v_degree()), uSize(surface->u_pole_num()),
          vSize(surface->v_pole_num()), uKnots(surface->u_knots()), vKnots(surface->v_knots()), cpts(surface->poles())
    {
    }

    Adaptor_OCCBSplineSurface(Handle(Geom_BSplineSurface) surface)
        : uDegree(surface->UDegree()), vDegree(surface->VDegree()), uSize(surface->NbUPoles()),
          vSize(surface->NbVPoles()), uKnots(surface->UKnots(), surface->UMultiplicities()),
          vKnots(surface->VKnots(), surface->VMultiplicities()), cpts(nullptr), occCpts(&surface->Poles()),
          occWeights(surface->Weights())
    {
    }

    operator std::shared_ptr<geom::Geom_BSplineSurface<N, Rat>>() const
    {
        std::vector<double> uknots = uKnots;
        std::vector<double> vknots = vKnots;
        if (occCpts)
        {
            auto &cpts = occCpts;
            auto &weights = occWeights;
            std::vector<VecR> poles(cpts->NbRows() * cpts->NbColumns());
            for (int i = 0; i < cpts->NbRows(); ++i)
            {
                for (int j = 0; j < cpts->NbColumns(); ++j)
                {
                    auto row = i * cpts->NbColumns() + j;
                    for (int k = 0; k < N; ++k)
                        poles[row][k] = (*cpts)(i + 1, j + 1).Coord(k + 1);

                    if constexpr (Rat)
                        poles[row][N] = (*weights)(i + 1, j + 1);
                }
            }
            return std::make_shared<geom::Geom_BSplineSurface<N, Rat>>(std::move(poles), std::move(uknots),
                                                                       std::move(vknots), uDegree, vDegree);
        }
        else
        {
            std::vector<VecR> poles = cpts;
            return std::make_shared<geom::Geom_BSplineSurface<N, Rat>>(std::move(poles), std::move(uknots),
                                                                       std::move(vknots), uDegree, vDegree);
        }
    }

    operator Handle(Geom_BSplineSurface)() const
    {
        auto [UKnots, UMult] = uKnots.occ();
        auto [VKnots, VMult] = vKnots.occ();
        if (occCpts)
        {
            if constexpr (Rat)
                return new Geom_BSplineSurface(*occCpts, *occWeights, UKnots, VKnots, UMult, VMult, uDegree, vDegree);
            else
                return new Geom_BSplineSurface(*occCpts, UKnots, VKnots, UMult, VMult, uDegree, vDegree);
        }
        else
        {
            TColgp_Array2OfPnt vCpts(1, uSize, 1, vSize);
            TColStd_Array2OfReal vWeights(1, uSize, 1, vSize);
            for (int i = 0; i < uSize; ++i)
            {
                for (int j = 0; j < vSize; ++j)
                {
                    Vec p;
                    for (int k = 0; k < N; ++k)
                        p[k] = cpts[i * vSize + j][k];

                    vCpts.SetValue(i + 1, j + 1, Adaptor_OCCVec(p));
                    if constexpr (Rat)
                        vWeights.SetValue(i + 1, j + 1, cpts[i * vSize + j][N]);
                }
            }

            if constexpr (Rat)
                return new Geom_BSplineSurface(vCpts, vWeights, UKnots, VKnots, UMult, VMult, uDegree, vDegree);
            else
                return new Geom_BSplineSurface(vCpts, UKnots, VKnots, UMult, VMult, uDegree, vDegree);
        }
    }
};

} // namespace utils

} // namespace xi
